/*
 * RPG Maker MV/MZ "encryption" is hilarious.
 * File extension goes like: rpgmvp / png_ -> png, rpgmvm / m4a_ -> m4a, rpgmvo / ogg_ -> ogg.
 * 1. File starts with a 16 byte header (first 5 bytes "RPGMV")
 * 2. This is followed by the file's first 16 bytes XOR'd with the key
 * 3. The rest is the original unencrypted file.
 * Now the joke is, the first 16 bytes of every PNG file is a constant, so we can get the key from any image file...
 */
#include "main.h"

static uint64_t total, current;
static uint8_t haskey, rpgm_key[16];

#define MAXTILESETS 64
#define TILE_ID_MAX 8192

static short int tilesize = 48, tilesets[MAXTILESETS * (9 + TILE_ID_MAX)];

/**
 * Read tile sets
 */
static void parse_tilesets(char *path, char *json, uint64_t len)
{
    char *fn, *e, *end = json + len;
    int i, j, l, id = 0, n = 0;

    memset(tilesets, 0xff, sizeof(tilesets));
    if(!path || !json || !len) return;
    fn = path + strlen(path);

    for(; json < end && *json; json++) {
        if(!memcmp(json, "\"id\"", 4)) { json = getvalue(json + 4, &id); }
        if(id > 0 && id < MAXTILESETS) {
            if(!memcmp(json, "\"flags\"", 7)) {
                for(json += 7; json < end && *json && *json != '['; json++);
                for(l = 0, json++; json < end && *json && *json != ']' && *json != '{' && *json != '}'; json++) {
                    while(json < end && *json && (*json == ',' || *json == ' ' || *json == '\t' || *json == '\r' || *json == '\n')) json++;
                    json = getvalue(json, &i);
                    if(l < TILE_ID_MAX)
                        tilesets[id * (9 + TILE_ID_MAX) + 9 + l++] = i;
                }
            }
            if(!memcmp(json, "\"tilesetNames\"", 14)) {
                for(j = 0, json += 14; json < end && *json && *json != ']' && *json != '{' && *json != '}'; json++) {
                    if(*json == '\"') {
                        for(json++, e = json; e < end && *e && *e != '\"'; e++);
                        l = (uintptr_t)e - (uintptr_t)json;
                        if(j < 9 && l > 0 && l < NAMELEN - 8) {
                            memcpy(fn, json, l);
                            strcpy(fn + l, ".png");
                            if((i = sprites_addsheet(-1, path, NULL, 0)) == -1) {
                                strcpy(fn + l, ".png_");
                                if((i = sprites_addsheet(-1, path, NULL, 0)) == -1) {
                                    strcpy(fn + l, ".rpgmvp");
                                    i = sprites_addsheet(-1, path, NULL, 0);
                                }
                            }
                            tilesets[id * (9 + TILE_ID_MAX) + j] = i;
                            n++;
                            *fn = 0;
                        }
                        j++;
                        json = e;
                    }
                }
                id = 0;
            }
        }
    }
    if(verbose > 1) debug("tilesets '%s' (%lu bytes, %u sets, tilewidth %u, tileheight %u)", path, len, n, tilesize, tilesize);
}

/**
 * Read in one map
 */
static void parse_maps(char *path, char *json, uint64_t len)
{
    char name[NAMELEN] = { 0 }, *end = json + len, *fn;
    int i, l, m, b = 0, tid = 0, w = 0, h = 0, *map;

    if(!json || !len) return;
    for(; json < end && *json; json++) {
        if(*json == '{') b++;
        if(*json == '}') b--;
        if(!memcmp(json, "\"tilesetId\"", 11)) { json = getvalue(json + 11, &tid); }
        if(!memcmp(json, "\"name\"", 6) && b == 1) { json = getstr(json + 6, name, NAMELEN); }
        if(!memcmp(json, "\"displayName\"", 13)) { json = getstr(json + 13, name, NAMELEN); }
        if(!memcmp(json, "\"width\"", 7)) { json = getvalue(json + 7, &w); }
        if(!memcmp(json, "\"height\"", 8)) { json = getvalue(json + 8, &h); }
        if(tid > 0 && tid < MAXTILESETS && w > 0 && h > 0 && !memcmp(json, "\"data\"", 6)) {
            if(!name[0]) {
                if((fn = strrchr(path, '/')) || (fn = strrchr(path, '\\')))
                    memcpy(name, fn + 1, 6);
                else
                    sprintf(name, "Map%03u", nummaps + 1);
            }
            if(verbose > 1) debug("map '%s' (%lu bytes, tileset %u, width %u, height %u)", path, len, tid, w, h);
            map = map_data(map_add(nummaps + 1, name, tid, tilesize, tilesize, w, h, 5), 0);
            for(json += 6; json < end && *json && *json != '['; json++);
            for(l = 0, m = w * h * 5, json++; l < m && json < end && *json && *json != ']' && *json != '{' && *json != '}'; json++) {
                while(*json && (*json == ',' || *json == ' ' || *json == '\t' || *json == '\r' || *json == '\n')) json++;
                json = getvalue(json, &i);
                if(map && l < m)
                    map[l++] = i;
            }
            name[0] = 0; tid = w = h = 0;
        }
    }
}

/**
 * Decrypt a buffer
 */
static uint64_t decrypt_file(uint8_t *buf, uint64_t len)
{
    int i;

    if(!haskey || !buf || len < 32) return len;
    for(i = 0; i < 16; i++) buf[i] = buf[i + 16] ^ rpgm_key[i];
    memcpy(buf + 16, buf + 32, len - 32);
    return len - 16;
}

/**
 * Detect if this is a RPGM MV/MZ game
 */
int rpgm_detect(char *path, uint8_t *buf, uint64_t size)
{
    char tmp[PATH_MAX + FILENAME_MAX + 1], *fn;

    (void)size;
    if(!path || !*path || buf) return 0;
    strcpy(tmp, path);
    fn = tmp + strlen(tmp);
    strcpy(fn, "/www/data/System.json");
    if(fileexists(tmp)) return 1;
    strcpy(fn, "/data/System.json");
    if(fileexists(tmp)) return 1;
    strcpy(fn, "/nw.dll");
    return fileexists(tmp);
}

/**
 * Callback function for find() in rpgm_load()
 */
int rpgm_findkeycb(char *path)
{
    uint8_t tmp[32], pnghdr[16] = { 0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A, 0, 0, 0, 0x0D, 'I', 'H', 'D', 'R' };
    uint64_t size = 0;
    void *f;
    int i = strlen(path);

    if(((i > 5 && !memcmp(path + i - 5, ".png_", 5)) || (i > 7 && !memcmp(path + i - 7, ".rpgmvp", 7))) &&
        (f = fileopen(path, &size))) {
            fileread(f, tmp, sizeof(tmp));
            fileclose(f);
            if(!memcmp(tmp, "RPGMV", 5)) {
                for(i = 0; i < 16; i++) rpgm_key[i] = tmp[i + 16] ^ pnghdr[i];
                haskey = 1;
                return 0;
            }
    }
    return 1;
}

/**
 * Callback function for find() in rpgm_load()
 */
int rpgm_loadcb(char *path)
{
    uint8_t *buf, tmp[32];
    uint64_t size = 0;
    void *f = fileopen(path, &size);
    char *ext = strrchr(path, '.'), ismap = 0;

    current += size;
    if(f) {
        fileread(f, tmp, sizeof(tmp));
        fileclose(f);
        if(!memcmp(path + pathlen + 1, "data/Map", 8) && path[pathlen + 9] >= '0' && path[pathlen + 9] <= '9') {
            ext = NULL; ismap = 1;
        }
        if(size > 32 && (!ext || strcmp(ext, ".json")) && (ismap || fileknown(tmp) || !memcmp(tmp, "RPGMV", 5)) &&
            (buf = getfile(path, &size))) {
                if(!memcmp(tmp, "RPGMV", 5)) size = decrypt_file(buf, size);
                if(ismap) {
                    parse_maps(path, (char*)buf, size);
                    free(buf);
                } else
                if(!fileasset(-1, path + pathlen + 1, buf, size)) free(buf);
        }
    }
    status(LOADING, current, total);
    return 1;
}

/**
 * Load all assets from a RPGM MV/MZ game
 */
void rpgm_load(char *path, int isdir)
{
    uint8_t *buf, *key;
    uint64_t size;
    char tmp[PATH_MAX + FILENAME_MAX + 1], *fn;
    int i;

    if(!path || !*path || !isdir) return;

    status(LOADING, 0, 100);

    strcpy(tmp, path);
    fn = tmp + strlen(tmp);
    pathlen = strlen(tmp);
    current = 0; total = du(tmp);

    haskey = 0; memset(rpgm_key, 0, sizeof(rpgm_key));
    if(engine_keylen) {
        if(engine_keylen == sizeof(rpgm_key)) { haskey = 1; memcpy(rpgm_key, engine_key, sizeof(rpgm_key)); }
        else msgbox(0, BADKEYLEN, NULL);
    }
    strcpy(fn, "/www/data/System.json");
    if(!(buf = getfile(tmp, &size))) {
        strcpy(fn, "/data/System.json");
        buf = getfile(tmp, &size);
    } else
        pathlen += 4;
    if(buf) {
        if(!haskey && (key = (uint8_t*)strstr((const char*)buf, "\"encryptionKey\""))) {
            for(key += 16; *key && key[-1] != '\"'; key++);
            for(i = 0; *key && *key != '\"' && i < 16; i++, key += 2)
                rpgm_key[i] = gethex((char*)key, 2);
            haskey = 1;
        }
        if((key = (uint8_t*)strstr((const char*)buf, "\"gameTitle\""))) {
            for(key += 12; *key && key[-1] != '\"'; key++);
            for(i = 0; *key && *key != '\"' && i < (int)sizeof(engine_game) - 1; key++) {
                if(key[0] == '\\' && key[1] == '\"') { engine_game[i++] = '\"'; key++; }
                else engine_game[i++] = *key;
            }
            engine_game[i] = 0;
        }
        free(buf);
    }
    if(!haskey) {
        status(DETECTKEY, 0, 100);
        strcpy(fn, "/www/img");
        find(tmp, 1, rpgm_findkeycb);
        if(!haskey) {
            strcpy(fn, "/img");
            find(tmp, 1, rpgm_findkeycb);
        }
        status(LOADING, 0, 100);
    }
    if(haskey) {
        debug("encryption key %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
            rpgm_key[0], rpgm_key[1], rpgm_key[2], rpgm_key[3], rpgm_key[4], rpgm_key[5], rpgm_key[6], rpgm_key[7],
            rpgm_key[8], rpgm_key[9], rpgm_key[10], rpgm_key[11], rpgm_key[12], rpgm_key[13], rpgm_key[14], rpgm_key[15]);
        engine_features |= EF_DECRYPT;
    }

    *fn = 0;
    find(tmp, 1, rpgm_loadcb);

    strcpy(fn, "/www/data/Tilesets.json");
    if(!(buf = getfile(tmp, &size))) {
        strcpy(fn, "/data/Tilesets.json");
        buf = getfile(tmp, &size);
    }
    if(buf) {
        strcpy(fn, "img/tilesets/");
        parse_tilesets(fn, (char*)buf, size);
        free(buf);
    }
}

/**
 * Free any additional resources that rpgm_load() allocated
 */
void rpgm_unload(void)
{
}

/**
 * Callback function for find() in rpgm_decrypt()
 */
int rpgm_decryptcb(char *path)
{
    uint8_t *buf, tmp[16];
    uint64_t size = 0;
    void *f = fileopen(path, &size);
    int len;

    current += size;
    if(f) {
        fileread(f, tmp, sizeof(tmp));
        fileclose(f);
        if(size > 32 && !memcmp(tmp, "RPGMV", 5)) {
            if((buf = getfile(path, &size))) {
                len = strlen(path);
                if(len && path[len - 1] == '_') path[len - 1] = 0; else
                if(len > 7 && !memcmp(path + len - 7, ".rpgmvp", 7)) strcpy(path + len - 7, ".png"); else
                if(len > 7 && !memcmp(path + len - 7, ".rpgmvo", 7)) strcpy(path + len - 7, ".ogg"); else
                if(len > 7 && !memcmp(path + len - 7, ".rpgmvm", 7)) strcpy(path + len - 7, ".m4a"); else {
                    for(tmp[0] = path[len - 1]; len > 1 && path[len - 1] != '.'; len--);
                    memcpy(path + len, ".~", 2);
                    path[len + 2] = tmp[0]; path[len + 3] = 0;
                }
                if((size = decrypt_file(buf, size)))
                    putfile(path, buf, size, NULL, 0, NULL, 0);
                free(buf);
            }
        }
    }
    status(SAVING, current, total);
    return 1;
}

/**
 * Decrypt and unpack all files in a RPGM MV/MZ game
 */
void rpgm_decrypt(char *path, int isdir)
{
    char tmp[PATH_MAX + FILENAME_MAX + 1];

    if(!path || !*path || !isdir || !haskey) return;

    strcpy(tmp, path);

    status(SAVING, 0, 100);
    current = 0; total = du(tmp);
    find(tmp, 1, rpgm_decryptcb);
}

/******** ported from a game's js/rpg_core.js Tilemap class ********/
#define TILE_ID_B 0
#define TILE_ID_C 256
#define TILE_ID_D 512
#define TILE_ID_E 768
#define TILE_ID_A5 1536
#define TILE_ID_A1 2048
#define TILE_ID_A2 2816
#define TILE_ID_A3 4352
#define TILE_ID_A4 5888
#define isVisibleTile(tileId) ((tileId) > 0 && (tileId) < TILE_ID_MAX)
#define isAutotile(tileId) ((tileId) >= TILE_ID_A1)
#define getAutotileKind(tileId) (((tileId) - TILE_ID_A1) / 48)
#define getAutotileShape(tileId) (((tileId) - TILE_ID_A1) % 48)
#define isSameKindTile(tileID1, tileID2) (isAutotile(tileID1) && isAutotile(tileID2) ? getAutotileKind(tileID1) == getAutotileKind(tileID2) : tileID1 == tileID2)
#define isTileA1(tileId) ((tileId) >= TILE_ID_A1 && (tileId) < TILE_ID_A2)
#define isTileA2(tileId) ((tileId) >= TILE_ID_A2 && (tileId) < TILE_ID_A3)
#define isTileA3(tileId) ((tileId) >= TILE_ID_A3 && (tileId) < TILE_ID_A4)
#define isTileA4(tileId) ((tileId) >= TILE_ID_A4 && (tileId) < TILE_ID_MAX)
#define isTileA5(tileId) ((tileId) >= TILE_ID_A5 && (tileId) < TILE_ID_A1)
#define isWaterTile(tileId) (isTileA1(tileId) ? !((tileId) >= TILE_ID_A1 + 96 && (tileId) < TILE_ID_A1 + 192) : 0)
#define isWaterfallTile(tileId) ((tileId) >= TILE_ID_A1 + 192 && (tileId) < TILE_ID_A2 ? getAutotileKind(tileId) % 2 == 1 : 0)
#define isGroundTile(tileId) (isTileA1(tileId) || isTileA2(tileId) || isTileA5(tileId))
#define isShadowingTile(tileId) (isTileA3(tileId) || isTileA4(tileId))
#define isRoofTile(tileId) (isTileA3(tileId) && getAutotileKind(tileId) % 16 < 8)
#define isWallTopTile(tileId) (isTileA4(tileId) && getAutotileKind(tileId) % 16 < 8)
#define isWallSideTile(tileId) ((isTileA3(tileId) || isTileA4(tileId)) && getAutotileKind(tileId) % 16 >= 8)
#define isWallTile(tileId) (isWallTopTile(tileId) || isWallSideTile(tileId))
#define isFloorTypeAutotile(tileId) ((isTileA1(tileId) && !isWaterfallTile(tileId)) || isTileA2(tileId) || isWallTopTile(tileId))
#define isWallTypeAutotile(tileId) (isRoofTile(tileId) || isWallSideTile(tileId))
#define isWaterfallTypeAutotile(tileId) isWaterfallTile(tileId)
#define isHigherTile(tilesetId, tileId) (tilesets[tilesetId * (9 + TILE_ID_MAX) + 9 + tileId] & 0x10)
#define isTableTile(tilesetId, tileId) (isTileA2(tileId) && (tilesets[tilesetId * (9 + TILE_ID_MAX) + 9 + tileId] & 0x80))
#define readMapData(x,y,l) map->data[(l * map->w * map->h) + y * map->w + x]
#define isOverpassPosition(x,y) (0)
static int FLOOR_AUTOTILE_TABLE[48 * 4 * 2] = {
   2, 4, 1, 4, 2, 3, 1, 3 ,   2, 0, 1, 4, 2, 3, 1, 3 ,   2, 4, 3, 0, 2, 3, 1, 3 ,   2, 0, 3, 0, 2, 3, 1, 3 ,
   2, 4, 1, 4, 2, 3, 3, 1 ,   2, 0, 1, 4, 2, 3, 3, 1 ,   2, 4, 3, 0, 2, 3, 3, 1 ,   2, 0, 3, 0, 2, 3, 3, 1 ,
   2, 4, 1, 4, 2, 1, 1, 3 ,   2, 0, 1, 4, 2, 1, 1, 3 ,   2, 4, 3, 0, 2, 1, 1, 3 ,   2, 0, 3, 0, 2, 1, 1, 3 ,
   2, 4, 1, 4, 2, 1, 3, 1 ,   2, 0, 1, 4, 2, 1, 3, 1 ,   2, 4, 3, 0, 2, 1, 3, 1 ,   2, 0, 3, 0, 2, 1, 3, 1 ,
   0, 4, 1, 4, 0, 3, 1, 3 ,   0, 4, 3, 0, 0, 3, 1, 3 ,   0, 4, 1, 4, 0, 3, 3, 1 ,   0, 4, 3, 0, 0, 3, 3, 1 ,
   2, 2, 1, 2, 2, 3, 1, 3 ,   2, 2, 1, 2, 2, 3, 3, 1 ,   2, 2, 1, 2, 2, 1, 1, 3 ,   2, 2, 1, 2, 2, 1, 3, 1 ,
   2, 4, 3, 4, 2, 3, 3, 3 ,   2, 4, 3, 4, 2, 1, 3, 3 ,   2, 0, 3, 4, 2, 3, 3, 3 ,   2, 0, 3, 4, 2, 1, 3, 3 ,
   2, 4, 1, 4, 2, 5, 1, 5 ,   2, 0, 1, 4, 2, 5, 1, 5 ,   2, 4, 3, 0, 2, 5, 1, 5 ,   2, 0, 3, 0, 2, 5, 1, 5 ,
   0, 4, 3, 4, 0, 3, 3, 3 ,   2, 2, 1, 2, 2, 5, 1, 5 ,   0, 2, 1, 2, 0, 3, 1, 3 ,   0, 2, 1, 2, 0, 3, 3, 1 ,
   2, 2, 3, 2, 2, 3, 3, 3 ,   2, 2, 3, 2, 2, 1, 3, 3 ,   2, 4, 3, 4, 2, 5, 3, 5 ,   2, 0, 3, 4, 2, 5, 3, 5 ,
   0, 4, 1, 4, 0, 5, 1, 5 ,   0, 4, 3, 0, 0, 5, 1, 5 ,   0, 2, 3, 2, 0, 3, 3, 3 ,   0, 2, 1, 2, 0, 5, 1, 5 ,
   0, 4, 3, 4, 0, 5, 3, 5 ,   2, 2, 3, 2, 2, 5, 3, 5 ,   0, 2, 3, 2, 0, 5, 3, 5 ,   0, 0, 1, 0, 0, 1, 1, 1
},
WALL_AUTOTILE_TABLE[16 * 4 * 2] = {
   2, 2, 1, 2, 2, 1, 1, 1 ,   0, 2, 1, 2, 0, 1, 1, 1 ,   2, 0, 1, 0, 2, 1, 1, 1 ,   0, 0, 1, 0, 0, 1, 1, 1 ,
   2, 2, 3, 2, 2, 1, 3, 1 ,   0, 2, 3, 2, 0, 1, 3, 1 ,   2, 0, 3, 0, 2, 1, 3, 1 ,   0, 0, 3, 0, 0, 1, 3, 1 ,
   2, 2, 1, 2, 2, 3, 1, 3 ,   0, 2, 1, 2, 0, 3, 1, 3 ,   2, 0, 1, 0, 2, 3, 1, 3 ,   0, 0, 1, 0, 0, 3, 1, 3 ,
   2, 2, 3, 2, 2, 3, 3, 3 ,   0, 2, 3, 2, 0, 3, 3, 3 ,   2, 0, 3, 0, 2, 3, 3, 3 ,   0, 0, 3, 0, 0, 3, 3, 3
},
WATERFALL_AUTOTILE_TABLE[4 * 4 * 2] = {
   2, 0, 1, 0, 2, 1, 1, 1 ,   0, 0, 1, 0, 0, 1, 1, 1 ,   2, 0, 3, 0, 2, 1, 3, 1 ,   0, 0, 3, 0, 0, 1, 3, 1
};

static void drawShadow(map_t *map, int shadowBits, int dx, int dy)
{
    int w1 = map->tw / 2;
    int h1 = map->th / 2;
    int i, dx1, dy1;

    if(shadowBits & 0x0f) {
        for(i = 0; i < 4; i++) {
            if(shadowBits & (1 << i)) {
                dx1 = dx + (i % 2) * w1;
                dy1 = dy + (i / 2) * h1;
                map_shadow(0x7f000000, map->tw / 2, map->th / 2, dx1, dy1);
            }
        }
    }
}

static void drawTableEdge(map_t *map, int tileId, int dx, int dy)
{
    int kind = getAutotileKind(tileId);
    int shape = getAutotileShape(tileId);
    int tx = kind % 8;
    int ty = kind / 8;
    int setNumber = 1;
    int bx = tx * 2;
    int by = (ty - 2) * 3;
    int *table = FLOOR_AUTOTILE_TABLE + shape * 8;
    int i, s, w1, h1, qsx, qsy, sx1, sy1, dx1, dy1;

    if(isTileA2(tileId)) {
        s = tilesets[map->tid * (9 + TILE_ID_MAX) + setNumber];
        w1 = map->tw / 2;
        h1 = map->th / 2;
        for(i = 0; i < 2; i++) {
            qsx = table[(2 + i) * 2 + 0];
            qsy = table[(2 + i) * 2 + 1];
            sx1 = (bx * 2 + qsx) * w1;
            sy1 = (by * 2 + qsy) * h1 + h1 / 2;
            dx1 = dx + (i % 2) * w1;
            dy1 = dy + (i / 2) * h1;
            map_tile(s, sx1, sy1, w1, h1 / 2, dx1, dy1);
        }
    }
}

static void drawAutotile(map_t *map, int tileId, int dx, int dy)
{
    int *autotileTable = FLOOR_AUTOTILE_TABLE;
    int kind = getAutotileKind(tileId);
    int shape = getAutotileShape(tileId);
    int tx = kind % 8;
    int ty = kind / 8;
    int bx = 0;
    int by = 0;
    int setNumber = 0;
    int isTable = 0;
    int *table, w1, h1, qsx, qsy, sx1, sy1, dx1, dy1, qsx2, qsy2, sx2, sy2;
    int i, s, qList[4] = { 0, 3, 2, 1 };

    if(isTileA1(tileId)) {
        setNumber = 0;
        switch(kind) {
            case 0: bx = 0; by = 0; break;
            case 1: bx = 0; by = 3; break;
            case 2: bx = 6; by = 0; break;
            case 3: bx = 6; by = 3; break;
            default:
                bx = (tx / 4) * 8;
                by = ty * 6 + ((tx / 2) % 2) * 3;
                if(kind % 2) {
                    bx += 6;
                    autotileTable = WATERFALL_AUTOTILE_TABLE;
                }
            break;
        }
    } else if(isTileA2(tileId)) {
        setNumber = 1;
        bx = tx * 2;
        by = (ty - 2) * 3;
        isTable = isTableTile(map->tid, tileId);
    } else if(isTileA3(tileId)) {
        setNumber = 2;
        bx = tx * 2;
        by = (ty - 6) * 2;
        autotileTable = WALL_AUTOTILE_TABLE;
    } else if(isTileA4(tileId)) {
        setNumber = 3;
        bx = tx * 2;
        by = (int)(((float)ty - 10.0) * 2.5 + (ty % 2 == 1 ? 0.5 : 0));
        if(ty % 2 == 1) {
            autotileTable = WALL_AUTOTILE_TABLE;
        }
    }
    table = autotileTable + shape * 8;
    s = tilesets[map->tid * (9 + TILE_ID_MAX) + setNumber];
    w1 = map->tw / 2;
    h1 = map->th / 2;
    for(i = 0; i < 4; i++) {
        qsx = table[i * 2 + 0];
        qsy = table[i * 2 + 1];
        sx1 = (bx * 2 + qsx) * w1;
        sy1 = (by * 2 + qsy) * h1;
        dx1 = dx + (i % 2) * w1;
        dy1 = dy + (i / 2) * h1;
        if(isTable && (qsy == 1 || qsy == 5)) {
            qsx2 = qsx;
            qsy2 = 3;
            if(qsy == 1) {
                qsx2 = qList[qsx];
            }
            sx2 = (bx * 2 + qsx2) * w1;
            sy2 = (by * 2 + qsy2) * h1;
            map_tile(s, sx2, sy2, w1, h1, dx1, dy1);
            dy1 += h1 / 2;
            map_tile(s, sx1, sy1, w1, h1 / 2, dx1, dy1);
        } else {
            map_tile(s, sx1, sy1, w1, h1, dx1, dy1);
        }
    }
}

static void drawNormalTile(map_t *map, int tileId, int dx, int dy)
{
    int setNumber = isTileA5(tileId) ? 4 : 5 + (tileId / 256);
    int sx = (((tileId / 128) % 2) * 8 + (tileId % 8)) * map->tw;
    int sy = (((tileId % 256) / 8) % 16) * map->th;
    map_tile(tilesets[map->tid * (9 + TILE_ID_MAX) + setNumber], sx, sy, map->tw, map->th, dx, dy);
}

static void drawTile(map_t *map, int tileId, int dx, int dy)
{
    if(isVisibleTile(tileId)) {
        if(isAutotile(tileId)) {
            drawAutotile(map, tileId, dx, dy);
        } else {
            drawNormalTile(map, tileId, dx, dy);
        }
    }
}

void drawMap(map_t *map, int mx, int my)
{
    int dx = (mx * map->tw);
    int dy = (my * map->th);
    int tileId0;
    int tileId1;
    int tileId2;
    int tileId3;
    int shadowBits;
    int upperTileId1;
    int lowerTiles[16], nlowerTiles = 0;
    int upperTiles[16], nupperTiles = 0;
    int i, lowerTileId;

    tileId0 = readMapData(mx, my, 0);
    tileId1 = readMapData(mx, my, 1);
    tileId2 = readMapData(mx, my, 2);
    tileId3 = readMapData(mx, my, 3);
    shadowBits = readMapData(mx, my, 4);
    upperTileId1 = my > 0 ? readMapData(mx, my - 1, 1) : 0;

    if(isHigherTile(map->tid, tileId0)) {
        upperTiles[nupperTiles++] = tileId0;
    } else {
        lowerTiles[nlowerTiles++] = tileId0;
    }
    if(isHigherTile(map->tid, tileId1)) {
        upperTiles[nupperTiles++] = tileId1;
    } else {
        lowerTiles[nlowerTiles++] = tileId1;
    }

    lowerTiles[nlowerTiles++] = -shadowBits;

    if(isTableTile(map->tid, upperTileId1) && !isTableTile(map->tid, tileId1)) {
        if(!isShadowingTile(tileId0)) {
            lowerTiles[nlowerTiles++] = 10000 + upperTileId1;
        }
    }

    if(isOverpassPosition(mx, my)) {
        upperTiles[nupperTiles++] = tileId2;
        upperTiles[nupperTiles++] = tileId3;
    } else {
        if(isHigherTile(map->tid, tileId2)) {
            upperTiles[nupperTiles++] = tileId2;
        } else {
            lowerTiles[nlowerTiles++] = tileId2;
        }
        if(isHigherTile(map->tid, tileId3)) {
            upperTiles[nupperTiles++] = tileId3;
        } else {
            lowerTiles[nlowerTiles++] = tileId3;
        }
    }

    for(i = 0; i < nlowerTiles; i++) {
        lowerTileId = lowerTiles[i];
        if(lowerTileId < 0) {
            drawShadow(map, shadowBits, dx, dy);
        } else if (lowerTileId >= 10000) {
            drawTableEdge(map, upperTileId1, dx, dy);
        } else {
            drawTile(map, lowerTileId, dx, dy);
        }
    }

    for(i = 0; i < nupperTiles; i++) {
        drawTile(map, upperTiles[i], dx, dy);
    }
}

/**
 * Draw map tiles to off-screen pixel buffer
 */
void rpgm_drawmap(map_t *map)
{
    int x, y;

    if(!map || !map->tw || !map->th || map->tid < 1 || map->tid >= MAXTILESETS) return;

    for(y = 0; y < map->h; y++)
       for(x = 0; x < map->w; x++)
           drawMap(map, x, y);
}

/**
 * Save translated text
 */
void rpgm_translate(char *path)
{
    msgbox(0, UNIMPLEMENTED, path);
}

ENGINE_MODULE(rpgm, "RPG Game Maker MV / MZ")
